#include "stdafx.h"
#include "APU.h"
#include "APU_INTERNAL.h"
#include "nes.h"
#include "cpu.h"
#include "ppu.h"

#define	RECTANGLE_VOL	(0x0F0)
#define	TRIANGLE_VOL	(0x130)
#define	NOISE_VOL	(0x0C0)
#define	DPCM_VOL	(0x0F0)
// Extra sounds
#define	VRC6_VOL	(0x0F0)
#define	VRC7_VOL	(0x130)
#define	FDS_VOL		(0x0F0)
#define	MMC5_VOL	(0x0F0)
#define	N106_VOL	(0x088)
#define	FME7_VOL	(0x130)
#define	ZEROMEMORY(p,s)	::memset( (p), 0, (s) )

SHORT	gVolume[16];

APU APU;
APU::APU( )
{
	exsound_select = 0;



	last_data = last_diff = 0;

	ZEROMEMORY( m_SoundBuffer, sizeof(m_SoundBuffer) );

	ZEROMEMORY( lowpass_filter, sizeof(lowpass_filter) );
	ZEROMEMORY( &queue, sizeof(queue) );
	ZEROMEMORY( &exqueue, sizeof(exqueue) );

	for( INT i = 0; i < 16; i++ ) {
		m_bMute[i] = TRUE;
	}
}

APU::~APU()
{
}

void	APU::SetQueue( INT writetime, WORD addr, BYTE data )
{
	queue.data[queue.wrptr].time = writetime;
	queue.data[queue.wrptr].addr = addr;
	queue.data[queue.wrptr].data = data;
	queue.wrptr++;
	queue.wrptr&=QUEUE_LENGTH-1;

}

BOOL	APU::GetQueue( INT writetime, QUEUEDATA& ret )
{
	if( queue.wrptr == queue.rdptr ) {
		return	FALSE;
	}
	if( queue.data[queue.rdptr].time <= writetime ) {
		ret = queue.data[queue.rdptr];
		queue.rdptr++;
		queue.rdptr&=QUEUE_LENGTH-1;
		return	TRUE;
	}
	return	FALSE;
}

void	APU::SetExQueue( INT writetime, WORD addr, BYTE data )
{
	exqueue.data[exqueue.wrptr].time = writetime;
	exqueue.data[exqueue.wrptr].addr = addr;
	exqueue.data[exqueue.wrptr].data = data;
	exqueue.wrptr++;
	exqueue.wrptr&=QUEUE_LENGTH-1;

}

BOOL	APU::GetExQueue( INT writetime, QUEUEDATA& ret )
{
	if( exqueue.wrptr == exqueue.rdptr ) {
		return	FALSE;
	}
	if( exqueue.data[exqueue.rdptr].time <= writetime ) {
		ret = exqueue.data[exqueue.rdptr];
		exqueue.rdptr++;
		exqueue.rdptr&=QUEUE_LENGTH-1;
		return	TRUE;
	}
	return	FALSE;
}

void	APU::QueueClear()
{
	ZEROMEMORY( &queue, sizeof(queue) );
	ZEROMEMORY( &exqueue, sizeof(exqueue) );
}

void	APU::QueueFlush()
{
	while( queue.wrptr != queue.rdptr ) {
		WriteProcess( queue.data[queue.rdptr].addr, queue.data[queue.rdptr].data );
		queue.rdptr++;
		queue.rdptr&=QUEUE_LENGTH-1;
	}

	while( exqueue.wrptr != exqueue.rdptr ) {
		WriteExProcess( exqueue.data[exqueue.rdptr].addr, exqueue.data[exqueue.rdptr].data );
		exqueue.rdptr++;
		exqueue.rdptr&=QUEUE_LENGTH-1;
	}
}

void	APU::SoundSetup()
{
	FLOAT	fClock = 1789772.5;
	INT	nRate = 22050;
			for( INT i = 0; i < 16; i++ ) {
			gVolume[i] = 100;
		}
	internal.Setup( fClock, nRate );

}

void	APU::Reset()
{
	ZEROMEMORY( &queue, sizeof(queue) );
	ZEROMEMORY( &exqueue, sizeof(exqueue) );

	elapsed_time = 0;

	FLOAT	fClock = 1789772.5;
	INT	nRate = 22050;
	
	internal.Reset( fClock, nRate );


	SoundSetup();
}

void	APU::SelectExSound( BYTE data )
{
	exsound_select = data;
}

BYTE	APU::Read( WORD addr )
{
	return	internal.SyncRead( addr );
}

void	APU::Write( WORD addr, BYTE data )
{
	
	if( addr >= 0x4000 && addr <= 0x401F ) {
		internal.SyncWrite( addr, data );
		SetQueue(CPU.get_total_cycle(), addr, data );
	}
}

BYTE	APU::ExRead( WORD addr )
{
return 0;
}

void	APU::ExWrite( WORD addr, BYTE data )
{

}

void	APU::Sync()
{
}

void	APU::SyncDPCM( INT cycles )
{

}

void	APU::WriteProcess( WORD addr, BYTE data )
{

	if( addr >= 0x4000 && addr <= 0x401F ) {
		internal.Write( addr, data );
	}
}

void	APU::WriteExProcess( WORD addr, BYTE data )
{
	
}

void	APU::Process( LPBYTE lpBuffer, DWORD dwSize )
{
INT	nBits = 8;
DWORD	dwLength = dwSize / (nBits/8);
INT	output;
QUEUEDATA q;
DWORD	writetime;

LPSHORT	pSoundBuf = m_SoundBuffer;
INT	nCcount = 0;

INT	nFilterType =0;


	// Volume setup
	//  0:Master
	//  1:Rectangle 1
	//  2:Rectangle 2
	//  3:Triangle
	//  4:Noise
	//  5:DPCM

	INT	vol[24];
	BOOL*	bMute = m_bMute;
	SHORT*	nVolume = gVolume;

	INT	nMasterVolume = bMute[0]?nVolume[0]:0;

	// Internal
	vol[ 0] = bMute[1]?(RECTANGLE_VOL*nVolume[1]*nMasterVolume)/(100*100):0;
	vol[ 1] = bMute[2]?(RECTANGLE_VOL*nVolume[2]*nMasterVolume)/(100*100):0;
	vol[ 2] = bMute[3]?(TRIANGLE_VOL *nVolume[3]*nMasterVolume)/(100*100):0;
	vol[ 3] = bMute[4]?(NOISE_VOL    *nVolume[4]*nMasterVolume)/(100*100):0;
	vol[ 4] = bMute[5]?(DPCM_VOL     *nVolume[5]*nMasterVolume)/(100*100):0;


	double	cycle_rate = ((double)29830*60.0/12.0)/(double)22050;


	if( elapsed_time >CPU.get_total_cycle()) {
		QueueFlush();
	}

	while( dwLength-- ) {
		writetime = (DWORD)elapsed_time;

		while( GetQueue( writetime, q ) ) {
			WriteProcess( q.addr, q.data );
		}

		while( GetExQueue( writetime, q ) ) {
			WriteExProcess( q.addr, q.data );
		}


		output = 0;
		output += internal.Process( 0 )*vol[0];
		output += internal.Process( 1 )*vol[1];
		output += internal.Process( 2 )*vol[2];
		output += internal.Process( 3 )*vol[3];
		output += internal.Process( 4 )*vol[4];



		output >>= 8;


#if	1
	
		{
//		static	double	cutoff = (2.0*3.141592653579*40.0/44100.0);
		static	double	cutofftemp = (2.0*3.141592653579*40.0);
		double	cutoff = cutofftemp/(double)22050;
		static	double	tmp = 0.0;
		double	in, out;

		in = (double)output;
		out = (in - tmp);
		tmp = tmp + cutoff * out;

		output = (INT)out;
		}
#endif
		// Limit
		if( output > 0x7FFF ) {
			output = 0x7FFF;
		} else if( output < -0x8000 ) {
			output = -0x8000;
		}

		if( nBits != 8 ) {
			*(SHORT*)lpBuffer = (SHORT)output;
			lpBuffer += sizeof(SHORT);
		} else {
			*lpBuffer++ = (output>>8)^0x80;
		}

		if( nCcount < 0x0100 )
			pSoundBuf[nCcount++] = (SHORT)output;


		elapsed_time += cycle_rate;
	}

#if	1
	if( elapsed_time > ((29830/24)+CPU.get_total_cycle()) ) {
		elapsed_time = CPU.get_total_cycle();
	}
	if( (elapsed_time+(29830/6)) < CPU.get_total_cycle()) {
		elapsed_time = CPU.get_total_cycle();
	}
#else
	elapsed_time = nes->cpu->GetTotalCycles();
#endif
}


INT	APU::GetChannelFrequency( INT no )
{
	if( !m_bMute[0] )
		return	0;

	
	if( no < 5 ) {
		return	m_bMute[no+1]?internal.GetFreq( no ):0;
	}

	return	0;
}


void	APU::SaveState( LPBYTE p )
{



	QueueFlush();

	internal.SaveState( p );
	p += (internal.GetStateSize()+15)&(~0x0F);	


}

void	APU::LoadState( LPBYTE p )
{

	QueueClear();

	internal.LoadState( p );
	p += (internal.GetStateSize()+15)&(~0x0F);	// Padding

}


